﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Messaging;
using System.Text;
using System.Threading.Tasks;
using HotShot.Service.Entities;
using HotShot.Entities.Messages;
using HotShot.Entities;

namespace HotShot.Service.BusinessLogic
{
    public class QueueCommunicationLogic
    {
        static QueueCommunicationLogic()
        {
            EntityBaseExtender.Init(new List<Type>
            {
                typeof(ProcessorNodeDescriptor),
                typeof(List<ProcessorNodeDescriptor>),
            });
        }

        public static QueueCommunicationLogic Current = new QueueCommunicationLogic();

        DateTime startupTime = DateTime.UtcNow;

        MessageQueue globalInputQueue;
        MessageQueue globalOutputQueue;
        MessageQueue localInputQueue;

        public void Initialize()
        {
            globalInputQueue = InitMq(null, "in");
            globalOutputQueue = InitMq(null, "out");
            localInputQueue = InitMq(ClusterProcessorLogic.Current.ProcessorId, "in");
        }

        protected MessageQueue InitMq(string nodeId, string direction)
        {
            string path;
            if (String.IsNullOrEmpty(nodeId)) path = @".\private$\hotshot." + direction;
            else path = String.Format(@".\private$\hotshot.{0}.{1}", nodeId, direction);

            var result = new MessageQueue(path)
            {
                Formatter = new XmlMessageFormatter(new[] { typeof(BaseMessage) }),
            };
            result.MessageReadPropertyFilter.CorrelationId = true;
            result.MessageReadPropertyFilter.SentTime = true;
            return result;
        }

        public Message GetGlobalMessage()
        {
            try
            {
                var message = globalInputQueue.Receive(TimeSpan.FromMilliseconds(100));
                if (message.SentTime.ToUniversalTime() < startupTime)
                    message = null;
                return message;
            }
            catch (MessageQueueException)
            {
            }
            return null;
        }

        public Message GetLocalMessage()
        {
            try
            {
                var message = localInputQueue.Receive(TimeSpan.FromMilliseconds(100));
                if (message.SentTime.ToUniversalTime() < startupTime)
                    message = null;
                return message;
            }
            catch (MessageQueueException)
            {
            }
            return null;
        }

        public void PostLocalMessage(string messageId, BaseMessage message)
        {
            localInputQueue.Send(message);
        }

        public void SendInitMessage()
        {
            if (Common.TraceMessages)
                Trace.WriteLine(String.Format("Sending InitNode to global"));

            PostMessage(globalInputQueue, Messages.InitNode(), null);
        }

        public void SendOutputMessage(BaseMessage message, string correlationId)
        {
            if (Common.TraceMessages)
                Trace.WriteLine(String.Format("Sending output message {0} to {1}", message.MessageType, null));

            PostMessage(globalOutputQueue, message, correlationId);
        }

        public void SendInputMessage(string nodeId, BaseMessage message, string correlationId)
        {
            if (Common.TraceMessages)
                Trace.WriteLine(String.Format("Sending {0} to {1}", message.MessageType, nodeId));

            if (nodeId == null)
            {
                PostMessage(globalInputQueue, message, correlationId);
            }
            else
            {
                using (MessageQueue mq = InitMq(nodeId, "in"))
                {
                    PostMessage(mq, message, correlationId);
                }
            }
        }

        private void PostMessage(MessageQueue queue, BaseMessage message, string correlationId)
        {
            try
            {
                var mqMessage = new Message(message);
                if (correlationId != null) mqMessage.CorrelationId = correlationId;
                queue.Send(mqMessage);
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.Message);
            }
        }

        public void Broadcast(BaseMessage message, string correlationId, List<ProcessorNodeDescriptor> nodes = null)
        {
            if (nodes == null)
                nodes = ClusterProcessorLogic.Current.GetKnownNodes(false, false);
            if (Common.TraceMessages)
                Trace.WriteLine(String.Format("Broadcasting {0} to {1} node(s)", message.MessageType, nodes.Count()));

            try
            {
                Trace.Indent();
                foreach (var node in nodes)
                    SendInputMessage(node.NodeId, message, correlationId);
            }
            finally
            {
                Trace.Unindent();
            }
        }
    }
}